单元1 搭建场景 本教程将会带大家实现一款经典的小游戏

您所在的位置:网站首页 sans像素图 方格 单元1 搭建场景 本教程将会带大家实现一款经典的小游戏

单元1 搭建场景 本教程将会带大家实现一款经典的小游戏

2023-06-30 17:16| 来源: 网络整理| 查看: 265

单元1 搭建场景

本教程将会带大家实现一款经典的小游戏——像素鸟,英文名Flappy Bird。这是一款休闲动作游戏,玩家通过点击屏幕控制小鸟的起飞降落,躲开沿途的障碍物。游戏没有胜利,玩家通过创建新的最高分而不断地挑战自我。

任务1.1 导入美术资源包

1. 创建工程

打开Unity的可执行程序(本案例使用的是Unity2017.2.0f3),选择创建2D工程,如图所示。

 

2. 导入资源包

找到教程提供的资源包,如图所示。直接拖入到Unity的Project视图中。

 

3. 设置图片格式

如果创建工程选择了2D模式,那么系统会自动将导入的图片设置为Sprite类型,此步骤可忽略不用操作。如果选择了3D模式,需要手动设置。在Project视图中选中所有的图片,如图所示。

 

在右侧Inspector视图中,将Texture Type设置为Sprite(2D and UI),最后不要忘记单击下方的“Apply”按钮,如图所示。

 

任务1.2 创建背景

1. 新建场景

按“Ctrl+S”键,或者选择File→Save Scene命令,将场景命名为Game,保存。此时场景里只保留Camera一个游戏对象,如图所示。

 

2. 调整相机

如果创建工程的时候选择了3D模式,则需要调整相机的设置。选中Camera,在Camera组件里设置Projection为Orthographic,Size设置为1.75。

在Game视图中设置默认分辨率为1440*900,如图所示。

 

3. 添加大地

在image文件夹里找到back图片,这是我们的地面。直接拖拽到场景中,然后再复制2个,分别命名为back0、back1、back2。如图所示。

 

将它们的坐标分别设置为(-0.25,0,0)、(4.8,0,0)、(9.85,0,0),此时的场景应该是这样的。

 

接着,我们需要创建一个空游戏对象,命名为Ground,修改坐标为(0,0,0),然后将3个back都放到Ground下,成为其子物体,如图所示。

 

这样一来,在实现无尽地图时会比较方便。

4. 添加天空

在image文件夹里找到bg,拖拽到场景中,同样再复制2个,命名为bg0、bg1、bg2。将他们的坐标分别设置为(-0.95,0,0)、(2.07,0,0)、(5.09,0,0)。同样新建一个空物体,命名为Sky,将3个bg对象都放到Sky下,成为其子物体。此时的场景应该是这样的。

 

天空遮住了地面,不用着急。选中3个bg对象,在Inspector的Sprite Renderer组件里修改Sorting Layer为-2。最终效果如图所示。

 

任务1.3 添加水管

1. 添加两根水管

从image文件夹中将pipe图片拖拽到场景中,命名为pipeUp,然后复制1个,命名为pipeDown,这就是我们的两个水管。将两个水管的Sprite Renderer中sorting layer修改为-1,让其介于地面和天空之间,如图所示。

 

将pipeUp的坐标修改为(0,-3,0),pipeDown的坐标修改为(0,3,0),效果如图所示。

 

2. 复制水管

为了表示这两个水管是一个整体,让PipeUp成为pipedown的子物体,如图所示。

 

将PipeDown复制出2个,命名为PipeDown1和PipeDown2。最后,设置PipeDown0的坐标为(3,-3,0),PipeDown1的坐标为(6,-3,0),PipeDown2的坐标为(9,-3,0),最后的效果如图所示。

 

3. 统一管理

同样的,为了方便后面生成无数个管子,我们需要创建一个空游戏对象,命名为GateGroup,修改坐标为(0,0,0),将三个PipeDown放到GateGroup下面,成为其子物体,如图所示。

 

最后,不要忘记按“Ctrl+S”键保存场景。

单元2 会飞的小鸟

场景已经搭建完成了,接下来该主角登场了。

任务2.1 创建小鸟

1. 添加小鸟游戏对象

将image文件夹里bird拖到场景中,重命名为Bird,可以看到效果如图。

 

这是因为bird图片的默认格式为Single。

2. 设置bird图片格式

在Project视图选中bird图片,在Inspector视图修改Sprite Mode为Multiple,然后点击Sprite Editor按钮,进入Sprite Editor窗口,对bird图片进行切割,如图所示。

 

设置三个方框的Position,分别如下图所示。

 

 

 

设置好后,单击Sprite Editor窗口中的“Apply”按钮,应用修改并关闭窗口。

最后,在Inspector视图下方,单击“Apply”按钮,应用对图片格式的修改。此时,Scene场景中的小鸟恢复了正常,如图所示。

 

任务2.2 起飞降落

1. 添加刚体

在Hierarchy视图中选中Bird,在Inspector视图中单击“Add Component”按钮添加Rigidbody 2D组件。设置Gravity Scale值为0.7,如图所示。

 

运行游戏,可以看到小鸟受重力影响而降落。

2. 添加SportCtrl.cs

创建SportCtrl.cs脚本,并添加到Bird游戏对象上。打开SportCtrl脚本,添加Rigidbody2D的字段并在Start方法中引用自身,如图所示。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

 

public class SportCtrl : MonoBehaviour

{

 

    Rigidbody2D rb;

    void Start()

    {

        rb = GetComponent();

    }

 

    // Update is called once per frame

    void FixedUpdate()

    {

        Fly();

 

    }

 

    public void Fly()

    {

        float xSpeed = 1f;

        Vector3 v = rb.velocity;

        float ySpeed = v.y;

        if (Input.GetMouseButton(0))

        {

            ySpeed = 2f;

        }

        rb.velocity = new Vector2(xSpeed, ySpeed);

    }

}

再运行游戏试一试,已经可以控制小鸟起飞降落了。

任务2.3 碰撞

现在小鸟已经可以飞了,但是会穿过地面和管子,这一节将会解决这个问题。

1. 添加小鸟的碰撞体

在Bird游戏对象上添加Circle Collider 2D,半径设置为0.18,如图所示。

 

2. 添加大地的碰撞体

给Ground下的三个子物体back0、back1、back2添加Box Collider 2D组件,并设置成如图所示的配置。

 

运行游戏,可以看到小鸟已经可以碰到地面了,如图所示。

 

3. 添加管子的碰撞体

选中所有的PipeDown和PipUp,如图所示。

 

全部都添加上Box Collider 2D组件即可。

单元3 无尽模式

任务3.1 相机跟随

1. 创建CamCtrl.cs脚本

在Main Camera上添加新脚本CamCtrl.cs,并完成以下代码。

public class CamCtrl : MonoBehaviour {

 

    public Transform target;

    Vector3 offset;

// Use this for initialization

void Start () {

        offset =  transform.position-target.position;

    }

// Update is called once per frame

void LateUpdate () {

        transform.position = target.position + offset;

}

}

回到编辑器,对Target进行赋值,如图所示。

 

运行游戏,发现我们的相机已经可以跟着小鸟移动了。

2. 限制相机高度

相机有时会穿帮,需要限制高度。修改脚本的LateUpdate方法如下。

void LateUpdate () {

        Vector3 pos= target.position + offset;

        if (pos.y > 0.9)

            pos.y = 0.9f;

        else if (pos.y < -0.9)

            pos.y = -0.9f;

        transform.position = pos;

}

任务3.2 无限地图

我们的思路是,将有限的图片进行重复使用,从而实现无限地图的效果。当最左侧的图片不可见时,将其移动到最右侧。

1. 创建Endless.cs脚本

添加如下代码。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

 

public class Endless : MonoBehaviour {

    public float distance;

    void OnBecameInvisible(){

        transform.Translate(Vector3.right* distance * 3);

    }

}

2. 实现无限地面

然后将脚本添加到back0、back1、back2,并在编辑器中设置distance为5.05。如图所示。

 

运行游戏进行测试,注意测试的时候需要关闭Scene视图。可以看到地面已经可以无限延长了,如图所示。

 

3.  实现无限天空

同理,给bg0、bg1、bg2添加Endless脚本,并设置distance为3.02。

运行游戏测试,可以看到如图效果。

 

4. 实现无限水管

同理,给pipeDown0、pipeDown1、pipeDown2添加Endless脚本,并设置distance为3。

运行游戏测试,可以看到水管也有无限个了。

任务3.3 水管随机高度

1. 创建RandomHeight.cs脚本

创建RandomHeight.cs脚本,添加如下代码。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

 

public class RandomHeight : MonoBehaviour

{

    void OnBecameVisible()

    {

        Vector3 pos = transform.position;

        pos.y = Random.Range(-3f, -1.5f);

        transform.position = pos;

    }

}

2. 添加脚本

选中所有的pipeDown,全都添加RandomHeight脚本。

运行游戏,测试效果。可以看到管子已经可以产生随机高度了。

 

单元4 游戏逻辑

任务4.1 UI

1. 创建开始界面

在场景中创建画布Canvas,然后制作开始界面。

首先在Canvas下新建一个空物体,命名为StartWnd。然后在StartWnd下新建一个Image,命名为imgTitle,SourceImage使用main图片。然后在StartWnd下新建一个Button,命名为btnStart,SourceImage使用start图片。如图所示。

 

调整位置,最后效果如图所示。

 

2. 创建准备界面

隐藏StartWnd,在Canvas下新建一个空物体,命名为ReadyWnd。然后在ReadyWnd下新建一个Button,命名为page1,SourceImage使用tap图片,效果如图所示。

 

隐藏page1,然后在ReadyWnd下新建一个Image,命名为page2,SourceImage使用ready图片。如图所示。

 

调整位置,效果如图所示。

 

3. 创建结束界面

隐藏ReadyWnd,在Canvas下新建一个空物体,命名为EndWnd。然后在EndWnd下新建一个Button,命名为page1,SourceImage使用gameover图片,效果如图所示。

 

隐藏page1,然后在EndWnd下新建一个空游戏对象,命名为page2。在page2下新建一个Image,命名为bg,SourceImage使用score图片。在bg下新建两个Text,一个命名为txtScore,一个命名为txtBest。在page2下新建一个Button,命名为btnRestart,SourceImage使用start图片,如图所示。

 

调整样式和位置,最后效果如图所示。

 

4. 创建左上角得分显示

在Canvas下新建Text,命名为txtScore。调整参数,最后效果如图所示。

 

任务4.2 创建游戏管理器

1. 创建GameRoot.cs脚本单例

创建空游戏对象GameRoot,并添加GameRoot.cs脚本。代码如下。

public class GameRoot : MonoBehaviour

{

    public static GameRoot Instance;

    void Start()

    {

        Instance = this;

}

}

2. 创建游戏状态

public const int GAMESTART = 0;

    public const int GAMEREADY = 1;

    public const int GAMERUN = 2;

    public const int GAMEEND = 3;

public int GAMESTATE = GAMESTART;

3. 引用UI窗口和主角

添加引用UI窗口和主角的字段,代码如下。

public Transform StartWnd, ReadyWnd, EndWnd, bird;

    public Text txtScore;

    public int score=0;

在外部对这些字段进行赋值,如图所示。

 

4. 创建游戏状态变更的方法

/// 

    /// 刚进入游戏时

    /// 

    public void Enter() {

        StartWnd.gameObject.SetActive(true);

        GAMESTATE = GAMESTART;

    }

 

    /// 

    /// 进入准备状态时

    /// 

    public void Ready() {

        bird.position = Vector3.zero;

        StartWnd.gameObject.SetActive(false);

        ReadyWnd.gameObject.SetActive(true);

        GAMESTATE = GAMEREADY;

        bird.rotation = Quaternion.identity;

    }

    /// 

    /// 进入娱乐状态时

    /// 

    public void Run() {

        ReadyWnd.gameObject.SetActive(false);

        GAMESTATE = GAMERUN;

    }

    /// 

    /// 游戏结束时

    /// 

    public void End()

    {

        EndWnd.gameObject.SetActive(true);

        GAMESTATE = GAMEEND;

        

    }

任务4.3 UI逻辑

1. 添加StartWnd脚本

首先创建StartWnd.cs脚本,代码如下:

using UnityEngine;

using UnityEngine.UI;

 

public class StartWnd : MonoBehaviour

{

    public Button btnStart;

    // Use this for initialization

    void Start()

    {

        btnStart.onClick.AddListener(OnStartClick);

 

    }

 

    void OnStartClick()

    {

        GameRoot.Instance.Ready();

    }

}

将脚本添加到StartWnd上,然后对UI字段进行赋值。

 

2. 添加ReadyWnd脚本

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;

 

public class ReadyWnd : MonoBehaviour

{

 

    public Button page1;

    public Transform page2;

    // Use this for initialization

    void Start()

    {

        page1.onClick.AddListener(OnClick);

    }

    void OnEnable()

    {

        page1.gameObject.SetActive(true);

        page2.gameObject.SetActive(false);

    }

 

    public void OnClick()

    {

        //显示page2

        page1.gameObject.SetActive(false);

        page2.gameObject.SetActive(true);

        //1秒过后调用

        StartCoroutine(Run());

    }

 

    IEnumerator Run() {

        yield return new WaitForSeconds(1);

        GameRoot.Instance.Run();

    

    }

 

}

将脚本添加到ReadyWnd上,并对外部引用进行赋值。如图所示。

 

3. 添加EndWnd脚本

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

using UnityEngine.UI;

 

public class EndWnd : MonoBehaviour

{

 

    public Button page1;

    public Transform page2;

    public Button btnRestart;

    public Text txtScore, txtBest;

    // Use this for initialization

    void Start()

    {

        page1.onClick.AddListener(OnPage1Click);

        btnRestart.onClick.AddListener(OnRestartClick);

        

    }

    void OnEnable() {

        page1.gameObject.SetActive(true);

        page2.gameObject.SetActive(false);

    }

 

    // Update is called once per frame

    void Update()

    {

 

    }

 

    void OnPage1Click()

    {

        page2.gameObject.SetActive(true);

        page1.gameObject.SetActive(false);

        txtBest.text = PlayerPrefs.GetInt("best").ToString();

        txtScore.text = GameRoot.Instance.score.ToString();

    }

    void OnRestartClick()

    {

        gameObject.SetActive(false);

        GameRoot.Instance.Ready();

    }

 

}

将脚本添加到EndWnd上,并给外部引用赋值,如图所示。

任务4.4 得分

1. 添加得分触发器

在pipeDown0下创建空游戏对象,命名为scoreTrigger。

 

设置Position为(0,3,0),然后在scoreTrigger上添加Box Collider 2D组件,设置如图所示。

 

效果如图所示。

 

同理,在pipeDown1和pipeDown2下也创建scoreTrigger。最后效果如图所示。

 

2. 添加得分方法

在GameRoot中添加以下方法,代码如下。

public void GetPoint()

    {

        score++;

        txtScore.text = "Score:" + score;

    }

3. 添加得分触发方法

在GateGroup上创建ScoreCtrl脚本,代码如下。

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

 

public class ScoreCtrl : MonoBehaviour {

 

    public void OnTriggerEnter2D(Collider2D other) {

        if (other.CompareTag("Player")) {

            GameRoot.Instance.GetPoint();

        }

    }

}

为了让这个方法对所有Trigger有效,在GateGroup上添加RigidBody2D组件,并设置组件,如图所示。

 

最后,修改Bird对象的Tag为Player。

任务4.5 失败

添加失败触发方法

在SportCtrl脚本里添加碰撞检测方法,代码如下。

    void OnCollisionEnter2D(Collision2D coll)

    {

        GameRoot.Instance.End();

    }

单元5 逻辑优化

任务5.1 控制优化

1. 禁用控制

修改SportCtrl.cs的FixedUpdate方法,代码如下。

void FixedUpdate()

    {

        if (GameRoot.Instance.GAMESTATE != GameRoot.GAMERUN)

            return;

        Fly();

    }

2. 禁用和启用重力

在SportCtrl.cs中添加两个方法:

public void EnableGravity() {

        rb.gravityScale = 0.7f;

    }

    public void DisEnableGravity() {

        rb.gravityScale = 0;

    }

然后添加SportCtrl单例并在游戏开始消除重力:

    public static SportCtrl Instance;

    void Start()

    {

        Instance = this;

        rb = GetComponent();

        DisEnableGravity();

    }

在GameRoot中调用:

public void Ready()

    {

        SportCtrl.Instance.DisEnableGravity();

        bird.position = Vector3.zero;

        StartWnd.gameObject.SetActive(false);

        ReadyWnd.gameObject.SetActive(true);

        GAMESTATE = GAMEREADY;

        bird.rotation = Quaternion.identity;

    }public void Run()

    {

        SportCtrl.Instance.EnableGravity();

        ReadyWnd.gameObject.SetActive(false);

        GAMESTATE = GAMERUN;

    }

3. 禁用和启用速度

在SportCtrl里添加方法:

   public void Init() {

        rb.velocity = Vector3.zero;

        rb.angularVelocity = 0;

    }

在GameRoot里调用:

public void Ready()

    {

        SportCtrl.Instance.Init();

        SportCtrl.Instance.DisEnableGravity();

        bird.position = Vector3.zero;

        StartWnd.gameObject.SetActive(false);

        ReadyWnd.gameObject.SetActive(true);

        GAMESTATE = GAMEREADY;

        bird.rotation = Quaternion.identity;

    }

任务5.2 场景重置

1. 在GameRoot游戏对象上添加MapManager脚本

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

 

public class MapManager : MonoBehaviour {

 

 

    public List maps = new List();

    List pos = new List();

// Use this for initialization

void Start () {

        for (var e in maps) {

            pos.Add(e.position);

        }

}

 

    public void Init () {

        for (int i = 0; i < maps.Count; i++)

        {

            maps[i].position = pos[i];

        }

    }

}

2. 在外部赋值

 

3. 在GameRoot里调用

public void Ready()

{

        GetComponent(). Init ();

        SportCtrl.Instance. Init ();

        SportCtrl.Instance.DisEnableGravity();

        bird.position = Vector3.zero;

        StartWnd.gameObject.SetActive(false);

        ReadyWnd.gameObject.SetActive(true);

        GAMESTATE = GAMEREADY;

        bird.rotation = Quaternion.identity;

    }

任务5.3 保存分数

1. 读取历史最高分

在GameRoot中添加最高分变量:

  public int best;

在游戏结束时,判断是否更新最高分:

public void End()

{

if (GAMESTATE == GAMEEND)

            return;        

EndWnd.gameObject.SetActive(true);

        GAMESTATE = GAMEEND;

        int best=PlayerPrefs.GetInt("best");

        if (score > best)

        {

            PlayerPrefs.SetInt("best", score);

        }

    }

2. 重置分数

在GameRoot里修改方法:

public void Ready()

    {

        score = 0;

        GetComponent().Init();

        SportCtrl.Instance. Init ();

        SportCtrl.Instance.DisEnableGravity();

        bird.position = Vector3.zero;

        StartWnd.gameObject.SetActive(false);

        ReadyWnd.gameObject.SetActive(true);

        GAMESTATE = GAMEREADY;

        bird.rotation = Quaternion.identity;

    }

单元6 声音和动画

任务6.1 添加声音

1. 在GameRoot上添加AudioManager脚本

using System.Collections;

using System.Collections.Generic;

using UnityEngine;

 

public class AudioManager : MonoBehaviour

{

    public static AudioManager Instance;

    public AudioSource fgSource;

    public AudioSource bgSource;

    public AudioClip wing;

    public AudioClip hit;

    public AudioClip die;

    public AudioClip point;

    public AudioClip swooshing;

 

    void Start() {

        Instance = this;

    }

    public void PlayHit() {

        fgSource .PlayOneShot(hit);

    }

    public void PlayWing()

    {

        if (!fgSource.isPlaying)

            fgSource.PlayOneShot(wing);

}

    public void PlayPoint()

    {

        bgSource.PlayOneShot(point);

    }

    public void PlaySwooshing()

    {

        fgSource .PlayOneShot(swooshing);

    }

    public void PlayDie()

    {

        fgSource .PlayOneShot(die);

    }

}

在GameRoot下创建两个空物体fgsource和bgsource,均添加AudioSource组件。

 

2. 在外部对AudioManager进行赋值

 

3. 在GameRoot中调用

public void Ready()

    {

        AudioManager.Instance.PlaySwooshing();

        score = 0;

        GetComponent(). Init ();

        SportCtrl.Instance. Init ();

        SportCtrl.Instance.DisEnableGravity();

        bird.position = Vector3.zero;

        StartWnd.gameObject.SetActive(false);

        ReadyWnd.gameObject.SetActive(true);

        GAMESTATE = GAMEREADY;

        bird.rotation = Quaternion.identity;

}

public void End()

    {

AudioManager.Instance.PlayHit();

if (GAMESTATE == GAMEEND)

            return;        

AudioManager.Instance.PlayDie();

 

EndWnd.gameObject.SetActive(true);

        GAMESTATE = GAMEEND;

        int best=PlayerPrefs.GetInt("best");

        if (score > best)

        {

            PlayerPrefs.SetInt("best", score);

        }

    }

public void GetPoint()

    {

        AudioManager.Instance.PlayPoint();

        score++;

        //AudioSvc.Instance.Playpoint();

        txtScore.text = "Score:" + score;

    }

4. 在SportCtrl中进行赋值

public void Fly()

    {

      

        float xSpeed = 1f;

        Vector3 v = rb.velocity;

        float ySpeed = v.y;

        if (Input.GetMouseButton(0))

        {

            ySpeed = 2f;

            AudioManager.Instance.PlayWing();

        }

        rb.velocity = new Vector3(xSpeed, ySpeed, 0);

    }

任务6.2 添加动画

1. 添加图片

在SportCtrl脚本中添加图片列表:

public SpriteRenderer renderer;

    public List sprites=new List();

    int index=0;

    float timer=0;

2. 在外部赋值

 

3. 切换图片

void SwitchSprite(float interval) {

        timer += Time.fixedDeltaTime;

        if (timer > interval) {

            timer = 0;

            index = (index + 1) % sprites.Count;

            renderer.sprite = sprites[index];

        }

    }

    // Update is called once per frame

    void FixedUpdate()

    {

        if (GameRoot.Instance.GAMESTATE != GameRoot.GAMERUN)

            return;

        Fly();

        SwitchSprite(0.1f);

    }

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3